﻿// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.Internal;
using System.Reflection.Emit;
using System.ComponentModel.Composition.Caching;

namespace System.ComponentModel.Composition
{
    // // Assume TMetadataView is
    // //interface Foo
    // //{
    // //    public typeRecord1 Record1 { get; }
    // //    public typeRecord2 Record2 { get; }
    // //    public typeRecord3 Record3 { get; }
    // //    public typeRecord4 Record4 { get; }
    // //}
    // // The class to generate will look like:
    // public class __Foo__MedataViewProxy : TMetadataView
    // {
    //     public __Foo__MedataViewProxy (IDictionary<string, object> metadata)
    //     {
    //         if(metadata == null)
    //         {
    //             throw InvalidArgumentException("metadata");
    //         }
    //         Record1 = metadata["Record1"];
    //         Record2 = metadata["Record2"];
    //         Record3 = metadata["Record3"];
    //         Record4 = metadata["Record4"];
    //     }
    //     // Interface
    //     public typeRecord1 Record1 { get; }
    //     public typeRecord2 Record2 { get; }
    //     public typeRecord3 Record3 { get; }
    //     public typeRecord4 Record4 { get; }
    // }
    internal static class MetadataViewGenerator
    {
        private static Lock _lock = new Lock();

        private static Dictionary<Type, Type> _proxies = new Dictionary<Type, Type>();

        private static AppDomain _localAppDomain = AppDomain.CurrentDomain;
        
        private static AssemblyName _mdvProxiesAssemblyName = 
            new AssemblyName(string.Format(CultureInfo.InvariantCulture, "MetadataViewProxies_{0}", Guid.NewGuid()));
        
        private static AssemblyBuilder _mdvProxiesAssemblyBuilder = 
            _localAppDomain.DefineDynamicAssembly(_mdvProxiesAssemblyName, AssemblyBuilderAccess.Run);
       
        private static ModuleBuilder _mdvProxiesModuleBuilder =
            _mdvProxiesAssemblyBuilder.DefineDynamicModule("MetadataViewProxiesModule");
        
        private static Type[] _mdvCtorArgumentTypes =
            new Type[] { typeof(IDictionary<string, object>) };

        private static MethodInfo _mdvDictionaryTryGet =
                _mdvCtorArgumentTypes[0].GetMethod("TryGetValue");

        private static ConstructorInfo _mdvObjectCtor =
            typeof(object).GetConstructor(Type.EmptyTypes);

        private static ConstructorInfo _mdvNotSupportedConstructor =
            typeof(NotSupportedException).GetConstructor(new Type[] { typeof(string) });

        public static Type GenerateView(Type viewType)
        {
            Assumes.NotNull(viewType);
            Assumes.IsTrue(viewType.IsInterface);

            Type proxyType;
            bool foundProxy;

            using (new ReadLock(_lock))
            {
                foundProxy = _proxies.TryGetValue(viewType, out proxyType);
            }

            // No factory exists
            if(!foundProxy)
            {
                // Try again under a write lock if still none generate the proxy
                using (new WriteLock(_lock))
                {
                    foundProxy = _proxies.TryGetValue(viewType, out proxyType);

                    if (!foundProxy)
                    {
                        proxyType = GenerateInterfaceViewProxyType(viewType);
                        Assumes.NotNull(proxyType);

                        _proxies.Add(viewType, proxyType);
                    }
                }
            }
            return proxyType;
        }

        // This must be called with _readerWriterLock held for Write
        private static Type GenerateInterfaceViewProxyType(Type viewType)
        {
            // View type is an interface let's cook an implementation
            Type proxyType;
            TypeBuilder proxyTypeBuilder;
            Type[] interfaces = { viewType };

            proxyTypeBuilder = _mdvProxiesModuleBuilder.DefineType(
                string.Format(CultureInfo.InvariantCulture, "_proxy_{0}_{1}", viewType.FullName, Guid.NewGuid()),
                TypeAttributes.Public,
                typeof(object),
                interfaces );

            // Implement Constructor
            Type[] ctorArguments = { viewType };
            ConstructorBuilder proxyCtorBuilder = proxyTypeBuilder.DefineConstructor(
                MethodAttributes.Public,
                CallingConventions.Standard,
                _mdvCtorArgumentTypes);

            ILGenerator proxyCtorIL = proxyCtorBuilder.GetILGenerator();
            proxyCtorIL.Emit(OpCodes.Ldarg_0);
            proxyCtorIL.Emit(OpCodes.Call, _mdvObjectCtor);

            // Implement interface properties
            foreach (PropertyInfo propertyInfo in viewType.GetAllProperties())
            {
                Type[] propertyTypeArguments = new Type[] { propertyInfo.PropertyType };
                string fieldName = string.Format(CultureInfo.InvariantCulture, "_{0}_{1}", propertyInfo.Name, Guid.NewGuid());
                string propertyName = string.Format(CultureInfo.InvariantCulture, "{0}", propertyInfo.Name);

                // Generate field
                FieldBuilder proxyFieldBuilder = proxyTypeBuilder.DefineField(
                    fieldName,
                    propertyInfo.PropertyType,
                    FieldAttributes.Private);

                // Generate property
                PropertyBuilder proxyPropertyBuilder = proxyTypeBuilder.DefineProperty(
                    propertyName,
                    PropertyAttributes.None,
                    propertyInfo.PropertyType,
                    (propertyInfo.CanWrite) ? propertyTypeArguments : null);

                LocalBuilder localValue = proxyCtorIL.DeclareLocal(typeof(object));
                Label doneTryGet = proxyCtorIL.DefineLabel();

                // In constructor set the backing field with the value from the dictionary
                proxyCtorIL.Emit(OpCodes.Ldarg_1);
                proxyCtorIL.Emit(OpCodes.Ldstr, propertyInfo.Name);
                proxyCtorIL.Emit(OpCodes.Ldloca, localValue);
                proxyCtorIL.Emit(OpCodes.Callvirt, _mdvDictionaryTryGet);
                proxyCtorIL.Emit(OpCodes.Brtrue_S, doneTryGet);

#if !SILVERLIGHT
                object[] attrs = propertyInfo.GetCustomAttributes(typeof(DefaultValueAttribute), false);
                if (attrs.Length > 0)
                {
                    DefaultValueAttribute defaultAttribute = (DefaultValueAttribute)attrs[0];
                    proxyCtorIL.LoadValue(defaultAttribute.Value);
                    if ((defaultAttribute.Value != null) && (defaultAttribute.Value.GetType().IsValueType))
                    {
                        proxyCtorIL.Emit(OpCodes.Box, defaultAttribute.Value.GetType());
                    }
                    proxyCtorIL.Emit(OpCodes.Stloc, localValue);
                }
                else
                {
                    // Throw exception
                    proxyCtorIL.LoadValue(string.Format(CultureInfo.CurrentCulture, Strings.MetadataItemNotSupported, propertyName));
                    proxyCtorIL.Emit(OpCodes.Newobj, _mdvNotSupportedConstructor);
                    proxyCtorIL.Emit(OpCodes.Throw);
                }
#endif
                proxyCtorIL.MarkLabel(doneTryGet);
                proxyCtorIL.Emit(OpCodes.Ldarg_0);
                proxyCtorIL.Emit(OpCodes.Ldloc, localValue);
                proxyCtorIL.Emit(propertyInfo.PropertyType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, propertyInfo.PropertyType);
                proxyCtorIL.Emit(OpCodes.Stfld, proxyFieldBuilder);

                if (propertyInfo.CanWrite)
                {
                    // Generate "set" method
                    MethodBuilder setMethodBuilder = proxyTypeBuilder.DefineMethod(
                        string.Format(CultureInfo.InvariantCulture, "set_{0}", propertyName),
                        MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.SpecialName,
                        null,
                        propertyTypeArguments);

                    proxyTypeBuilder.DefineMethodOverride(setMethodBuilder, propertyInfo.GetSetMethod());
                    ILGenerator setMethodIL = setMethodBuilder.GetILGenerator();
                    setMethodIL.LoadValue(string.Format(CultureInfo.CurrentCulture, Strings.SetMethodsNotSupported, propertyName));
                    setMethodIL.Emit(OpCodes.Newobj, _mdvNotSupportedConstructor);
                    setMethodIL.Emit(OpCodes.Throw);
                   
                    proxyPropertyBuilder.SetSetMethod(setMethodBuilder);
                }
                if(propertyInfo.CanRead)
                {
                    // Generate "get" property for this property as a method.
                    MethodBuilder getMethodBuilder = proxyTypeBuilder.DefineMethod(
                        string.Format(CultureInfo.InvariantCulture, "get_{0}", propertyName),
                        MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.SpecialName, 
                        propertyInfo.PropertyType, 
                        Type.EmptyTypes);

                    proxyTypeBuilder.DefineMethodOverride(getMethodBuilder, propertyInfo.GetGetMethod());
                    ILGenerator getMethodIL = getMethodBuilder.GetILGenerator();
                    getMethodIL.Emit(OpCodes.Ldarg_0);
                    getMethodIL.Emit(OpCodes.Ldfld, proxyFieldBuilder);
                    getMethodIL.Emit(OpCodes.Ret);
                    
                    proxyPropertyBuilder.SetGetMethod(getMethodBuilder);
                }
            }

            // Finished implementing interface and constructor
            proxyCtorIL.Emit(OpCodes.Ret);
            proxyType = proxyTypeBuilder.CreateType();

            return proxyType;
        }
             
    }
}
